package com.smx.sync.service;

import com.smx.sync.entity.SyncTask;
import com.smx.sync.mapper.SyncConfigMapper;
import com.smx.sync.util.DatabaseConnectionChecker;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import javax.annotation.PreDestroy;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * SyncService
 *
 * @author 廖功旺
 * @version 1.0
 * @since 2025/6/23 17:12
 */
@Slf4j
@Service
public class SyncService {

    @Autowired
    private SyncConfigMapper syncConfigMapper;
    @Autowired
    private TaskScheduler taskScheduler;
    @Autowired
    private SyncTaskExecutor syncTaskExecutor;

    // 记录每个任务的重试次数
    private final ConcurrentHashMap<Integer, AtomicInteger> retryCountMap = new ConcurrentHashMap<>();
    // 记录每个任务的最后重试时间
    private final ConcurrentHashMap<Integer, LocalDateTime> lastRetryTimeMap = new ConcurrentHashMap<>();
    // 存储当前有效的定时任务
    private final ConcurrentHashMap<Integer, Runnable> scheduledTasks = new ConcurrentHashMap<>();
    // 用于定期检查连接失败的任务
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    // 最大重试次数
    private static final int MAX_RETRY_COUNT = 5;
    // 重试时间窗口（分钟）
    private static final int RETRY_WINDOW_MINUTES = 30;
    // 定时检测间隔（分钟）
    private static final int CHECK_INTERVAL_MINUTES = 1;

    /**
     * 创建同步任务
     */
    public void createSyncTask() {
        List<SyncTask> syncTaskList = syncConfigMapper.getSyncTaskList();
        for (SyncTask task : syncTaskList) {
            // 初始化重试计数
            retryCountMap.putIfAbsent(task.getId(), new AtomicInteger(0));

            boolean isSourceDbConnected = checkSourceDbConnection(task);
            boolean isTargetDbConnected = checkTargetDbConnection(task);

            if (isSourceDbConnected && isTargetDbConnected) {
                // 如果数据库连接成功，则创建定时任务
                scheduleSyncTask(task);
            } else {
                // 如果数据库连接失败，则安排定期重试
                scheduleRetryTask(task);
                log.warn("任务ID：{} 数据库连接失败，已加入重试队列", task.getId());
            }
        }

        // 开始定期检查连接失败的任务
        startRetryScheduler();
    }

    /**
     * 创建定时任务
     */
    private void scheduleSyncTask(SyncTask task) {
        taskScheduler.schedule(() -> {
            try {
                syncTaskExecutor.executeSync(task);
            } catch (Exception e) {
                log.error("同步任务执行异常，任务ID：{}", task.getId(), e);
            }
        }, new CronTrigger(task.getCron()));

        log.info("任务ID：{} 已创建，源数据库和目标数据库连接成功，cron：{}", task.getId(), task.getCron());
    }

    /**
     * 安排定期重试任务
     */
    private void scheduleRetryTask(SyncTask task) {
        // 移除已有的重试任务
        scheduledTasks.remove(task.getId());

        // 创建新的重试任务
        Runnable retryRunnable = () -> {
            AtomicInteger retryCount = retryCountMap.get(task.getId());
            LocalDateTime now = LocalDateTime.now();

            // 检查是否超过最大重试次数
            if (retryCount.get() >= MAX_RETRY_COUNT) {
                // 检查是否超过了重试时间窗口
                LocalDateTime lastRetryTime = lastRetryTimeMap.getOrDefault(task.getId(), now.minusMinutes(RETRY_WINDOW_MINUTES + 1));

                if (now.isAfter(lastRetryTime.plusMinutes(RETRY_WINDOW_MINUTES))) {
                    // 时间窗口已过，重置重试次数
                    retryCount.set(0);
                    log.info("任务ID：{} 已超过重试时间窗口，重置重试次数", task.getId());
                } else {
                    log.warn("任务ID：{} 已达到最大重试次数，暂时无法创建同步任务", task.getId());
                    return;
                }
            }

            log.info("任务ID：{} 正在尝试重新连接数据库，当前重试次数：{}", task.getId(), retryCount.get());

            // 尝试重新连接数据库
            boolean sourceConnected = checkSourceDbConnection(task);
            boolean targetConnected = checkTargetDbConnection(task);

            if (sourceConnected && targetConnected) {
                log.info("任务ID：{} 数据库连接恢复，正在创建同步任务", task.getId());
                // 连接成功，创建同步任务
                scheduleSyncTask(task);
                // 清除重试计数
                retryCount.set(0);
            } else {
                // 更新最后重试时间
                lastRetryTimeMap.put(task.getId(), now);

                // 增加重试次数
                retryCount.incrementAndGet();

                log.warn("任务ID：{} 数据库连接仍未恢复，当前重试次数：{}", task.getId(), retryCount.get());
            }
        };

        // 添加到定时任务中
        scheduledTasks.put(task.getId(), retryRunnable);
    }

    /**
     * 开启定时重试调度器
     */
    public void startRetryScheduler() {
        scheduler.scheduleAtFixedRate(() -> {
            // 遍历所有任务，对于未连接成功的任务执行重试
            scheduledTasks.forEach((taskId, taskRunnable) -> {
                try {
                    taskRunnable.run();
                } catch (Exception e) {
                    log.error("执行重试任务时发生异常，任务ID：{}", taskId, e);
                }
            });
        }, 0, CHECK_INTERVAL_MINUTES, TimeUnit.MINUTES);
    }

    /**
     * 销毁方法，清理资源
     */
    @PreDestroy
    public void destroy() {
        scheduler.shutdownNow();
        scheduledTasks.clear();
    }

    /**
     * 检查源数据库连接
     *
     * @param task 任务
     * @return 是否连接成功
     */
    private boolean checkSourceDbConnection(SyncTask task) {
        return DatabaseConnectionChecker.checkConnection(
                task.getSourceDbDriver(),
                task.getSourceDbUrl(),
                task.getSourceDbUsername(),
                task.getSourceDbPassword());
    }

    /**
     * 检查目标数据库连接
     *
     * @param task 任务
     * @return 是否连接成功
     */
    private boolean checkTargetDbConnection(SyncTask task) {
        return DatabaseConnectionChecker.checkConnection(
                task.getTargetDbDriver(),
                task.getTargetDbUrl(),
                task.getTargetDbUsername(),
                task.getTargetDbPassword());
    }
}
